using System;
using System.Drawing;
using System.Linq;
using SkaldRogueLike.Main.Common;
using SkaldRogueLike.Main.Constants;

using SkaldRogueLike.Main.DungeonGenerating.DirectionProcessing;

namespace SkaldRogueLike.Main.DungeonGenerating
{
    public static class DungeonGenerator
    {
        private const int AlwaysChangeDirMod = 100;

        public static DungeonGenerating.MapImplementations.Dungeon CreateFinalMap(int width, int height, 
            int changeDirMod, int sparseness, int deadEndRemovalMod, 
            RoomGenerator roomGenerator)
        {
            var map = new DungeonGenerating.MapImplementations.Dungeon(width, height);
            GetMaze(map, changeDirMod, map.PickUpRandomCellAndVisitIt());
            SparsifyMap(map, sparseness);
            CreateLoops(map, deadEndRemovalMod);
            map = roomGenerator.PlaceRooms(map);
            CreateStairs(map);
            return map;
        }

        public static void CreateLoops(DungeonGenerating.MapImplementations.Dungeon dungeon, int deadEndRemovalMod)
        {
            var directionProcessor = new DirectionProcessor(AlwaysChangeDirMod);
            var deadends = dungeon.GetDeadEnds();
            foreach (var deadend in deadends.Where(it => ShouldRemoveDeadend(deadEndRemovalMod)))
            {
                var current = deadend;
                while (dungeon[current].IsDeadEnd)
                {
                    directionProcessor.RestoreDirs();
                    var currentDir = dungeon[current].GetDeadEndCorridorDir();
                    var dir = directionProcessor.GetNextDirection(currentDir);
                    while (!dungeon.HasAdjacentCell(current, dir))
                    {
                        if (directionProcessor.HasDirs)
                            dir = directionProcessor.GetNextDirection(currentDir);
                        else
                            throw new InvalidOperationException("This should not happen");
                    }
                    current = dungeon.CreateCorridor(current, dir);
                }
            }
        }

        public static void SparsifyMap(DungeonGenerating.MapImplementations.Dungeon dungeon, int sparseness)
        {
            var deadendsToRemove = (int) (sparseness / 100.0) * dungeon.Width * dungeon.Height;
            while (deadendsToRemove > 0)
            {
                var deadends = dungeon.GetDeadEnds().ToList();
                if (deadends.Count == 0)
                    break;
                foreach (var d in deadends.Where(d => dungeon[d].IsDeadEnd))
                {
                    dungeon.CreateWall(d, dungeon[d].GetDeadEndCorridorDir());
                    dungeon[d].IsCorridor = false;
                }
                deadendsToRemove--;
            }
        }

        private static void GetMaze(DungeonGenerating.MapImplementations.Dungeon dungeon, int changeDirMod, Point current)
        {
            var directionProcessor = new DirectionProcessor(changeDirMod);
            while (!dungeon.AllCellsVisited)
            {
                directionProcessor.RestoreDirs();
                var dir = directionProcessor.GetNextDirection();
                while (!IsValidDir(dungeon, current, dir))
                {
                    if (!directionProcessor.HasDirs)
                    {
                        current = dungeon.GetRandomVisitedCell(current);
                        directionProcessor.RestoreDirs();
                    }
                    dir = directionProcessor.GetNextDirection();
                }

                current = dungeon.CreateCorridor(current, dir);
                dungeon.VisitCell(current);
            }
        }

        private static bool IsValidDir(DungeonGenerating.MapImplementations.Dungeon dungeon, Point current, Dir dir)
        {
            return dungeon.HasAdjacentCell(current, dir) && !dungeon.IsAdjacentCellVisited(current, dir);
        }

        public static bool ShouldRemoveDeadend(int deadEndRemovalMod)
        {
            return deadEndRemovalMod > StaticRandom.Next(100);  
        }

        public static void CreateStairs(DungeonGenerating.MapImplementations.Dungeon dungeon)
        {
            var freeCells = dungeon.CorridorCellLocations.ToArray();
            if (freeCells.Length < 2)
                throw new InvalidOperationException("there are no place to put stairs");
            var downPoint = freeCells[StaticRandom.Next(freeCells.Length)];
            var upPoint = freeCells[StaticRandom.Next(freeCells.Length)];
            while(upPoint == downPoint)
                upPoint = freeCells[StaticRandom.Next(freeCells.Length)];
            dungeon.PlaceStairs(downPoint, upPoint);
        }
    }
}